#include "win_serial.h"
#include <QReadWriteLock>
Win_Serial::Win_Serial():
    QSerialBase()
{
    Win_Handle=INVALID_HANDLE_VALUE;
    init();
}

Win_Serial::Win_Serial(int iPort,
                       const PortSettings &settings,
                       QSerialBase::QueryMode mode):
    QSerialBase(iPort)
{
    Win_Handle=INVALID_HANDLE_VALUE;
    setBaudRate(settings.BaudRate);
    setDataBits(settings.DataBits);
    setStopBits(settings.StopBits);
    setParity(settings.Parity);
    setFlowControl(settings.FlowControl);
    setTimeout(settings.Timeout_Millisec);
    setQueryMode(mode);
    init();
}

Win_Serial::~Win_Serial()
{
    if (isOpen()) {
        close();
    }
    CloseHandle(overlap.hEvent);
    delete overlapThread;
    delete bytesToWriteLock;
}

void Win_Serial::init()
{
    _bytesToWrite = 0;
    overlap.Internal = 0;
    overlap.InternalHigh = 0;
    overlap.Offset = 0;
    overlap.OffsetHigh = 0;
    overlap.hEvent = CreateEvent(NULL, true, false, NULL);
    overlapThread = new Win_SerialThread(this);
    bytesToWriteLock = new QReadWriteLock;
}

void Win_Serial::setFlowControl(FlowType flow)
{
    LOCK_MUTEX();
    if (m_PortSettings.FlowControl!=flow) {
        m_PortSettings.FlowControl=flow;
    }
    if (isOpen())
    {
        switch(flow)
        {

            case FLOW_OFF:
                Win_CommConfig.dcb.fOutxCtsFlow=0;
                Win_CommConfig.dcb.fRtsControl=RTS_CONTROL_DISABLE;
                Win_CommConfig.dcb.fInX=0;
                Win_CommConfig.dcb.fOutX=0;
                SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                break;

            case FLOW_XONXOFF:
                Win_CommConfig.dcb.fOutxCtsFlow=FALSE;
                Win_CommConfig.dcb.fRtsControl=RTS_CONTROL_DISABLE;
                Win_CommConfig.dcb.fInX=TRUE;
                Win_CommConfig.dcb.fOutX=TRUE;
                SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                break;

            case FLOW_HARDWARE:
                Win_CommConfig.dcb.fOutxCtsFlow=TRUE;
                Win_CommConfig.dcb.fRtsControl=RTS_CONTROL_HANDSHAKE;
                Win_CommConfig.dcb.fInX=FALSE;
                Win_CommConfig.dcb.fOutX=FALSE;
                SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                break;
        }
    }
    UNLOCK_MUTEX();
}

void Win_Serial::setBaudRate(BaudRateType baudRate)
{
    LOCK_MUTEX();
    if (m_PortSettings.BaudRate!=baudRate) {
        switch (baudRate) {
            case BAUD50:
            case BAUD75:
            case BAUD134:
            case BAUD150:
            case BAUD200:
                m_PortSettings.BaudRate=BAUD110;
                break;

            case BAUD1800:
                m_PortSettings.BaudRate=BAUD1200;
                break;

            case BAUD76800:
                m_PortSettings.BaudRate=BAUD57600;
                break;

            default:
                m_PortSettings.BaudRate=baudRate;
                break;
        }
    }
    if (isOpen()) {
        switch (baudRate) {

            /*50 baud*/
            case BAUD50:
                Win_CommConfig.dcb.BaudRate=CBR_110;
                break;

            /*75 baud*/
            case BAUD75:
                Win_CommConfig.dcb.BaudRate=CBR_110;
                break;

            /*110 baud*/
            case BAUD110:
                Win_CommConfig.dcb.BaudRate=CBR_110;
                break;

            /*134.5 baud*/
            case BAUD134:
                Win_CommConfig.dcb.BaudRate=CBR_110;
                break;

            /*150 baud*/
            case BAUD150:
                Win_CommConfig.dcb.BaudRate=CBR_110;
                break;

            /*200 baud*/
            case BAUD200:
                Win_CommConfig.dcb.BaudRate=CBR_110;
                break;

            /*300 baud*/
            case BAUD300:
                Win_CommConfig.dcb.BaudRate=CBR_300;
                break;

            /*600 baud*/
            case BAUD600:
                Win_CommConfig.dcb.BaudRate=CBR_600;
                break;

            /*1200 baud*/
            case BAUD1200:
                Win_CommConfig.dcb.BaudRate=CBR_1200;
                break;

            /*1800 baud*/
            case BAUD1800:
                Win_CommConfig.dcb.BaudRate=CBR_1200;
                break;

            /*2400 baud*/
            case BAUD2400:
                Win_CommConfig.dcb.BaudRate=CBR_2400;
                break;

            /*4800 baud*/
            case BAUD4800:
                Win_CommConfig.dcb.BaudRate=CBR_4800;
                break;

            /*9600 baud*/
            case BAUD9600:
                Win_CommConfig.dcb.BaudRate=CBR_9600;
                break;

            /*14400 baud*/
            case BAUD14400:
                Win_CommConfig.dcb.BaudRate=CBR_14400;
                break;

            /*19200 baud*/
            case BAUD19200:
                Win_CommConfig.dcb.BaudRate=CBR_19200;
                break;

            /*38400 baud*/
            case BAUD38400:
                Win_CommConfig.dcb.BaudRate=CBR_38400;
                break;

            /*56000 baud*/
            case BAUD56000:
                Win_CommConfig.dcb.BaudRate=CBR_56000;
                break;

            /*57600 baud*/
            case BAUD57600:
                Win_CommConfig.dcb.BaudRate=CBR_57600;
                break;

            /*76800 baud*/
            case BAUD76800:
                Win_CommConfig.dcb.BaudRate=CBR_57600;
                break;

            /*115200 baud*/
            case BAUD115200:
                Win_CommConfig.dcb.BaudRate=CBR_115200;
                break;

            /*128000 baud*/
            case BAUD128000:
                Win_CommConfig.dcb.BaudRate=CBR_128000;
                break;

            /*256000 baud*/
            case BAUD256000:
                Win_CommConfig.dcb.BaudRate=CBR_256000;
                break;
        }
        SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
    }
    UNLOCK_MUTEX();
}

void Win_Serial::setDataBits(DataBitsType dataBits)
{
    LOCK_MUTEX();
    if (m_PortSettings.DataBits!=dataBits)
    {
        if ((m_PortSettings.StopBits==STOP_2 && dataBits==DATA_5) ||
            (m_PortSettings.StopBits==STOP_1_5 && dataBits!=DATA_5))
        {
        }
        else
        {
            m_PortSettings.DataBits=dataBits;
        }
    }
    if (isOpen())
    {
        switch(dataBits)
        {

            /*5 data bits*/
            case DATA_5:
                if (m_PortSettings.StopBits==STOP_2)
                {
                }
                else {
                    Win_CommConfig.dcb.ByteSize=5;
                    SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                }
                break;

            /*6 data bits*/
            case DATA_6:
                if (m_PortSettings.StopBits==STOP_1_5)
                {
                }
                else
                {
                    Win_CommConfig.dcb.ByteSize=6;
                    SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                }
                break;

            /*7 data bits*/
            case DATA_7:
                if (m_PortSettings.StopBits==STOP_1_5)
                {
                }
                else
                {
                    Win_CommConfig.dcb.ByteSize=7;
                    SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                }
                break;

            /*8 data bits*/
            case DATA_8:
                if (m_PortSettings.StopBits==STOP_1_5)
                {
                }
                else
                {
                    Win_CommConfig.dcb.ByteSize=8;
                    SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                }
                break;
        }
    }
    UNLOCK_MUTEX();
}

void Win_Serial::setStopBits(StopBitsType stopBits)
{
    LOCK_MUTEX();
    if (m_PortSettings.StopBits!=stopBits)
    {
        if ((m_PortSettings.DataBits==DATA_5 && stopBits==STOP_2) ||
            (stopBits==STOP_1_5 && m_PortSettings.DataBits!=DATA_5))
        {
        }
        else
        {
            m_PortSettings.StopBits=stopBits;
        }
    }
    if (isOpen()) {
        switch (stopBits)
        {

            /*one stop bit*/
            case STOP_1:
                Win_CommConfig.dcb.StopBits=ONESTOPBIT;
                SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                break;

            /*1.5 stop bits*/
            case STOP_1_5:
                if (m_PortSettings.DataBits!=DATA_5)
                {
                }
                else
                {
                    Win_CommConfig.dcb.StopBits=ONE5STOPBITS;
                    SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                }
                break;

            /*two stop bits*/
            case STOP_2:
                if (m_PortSettings.DataBits==DATA_5)
                {
                }
                else
                {
                    Win_CommConfig.dcb.StopBits=TWOSTOPBITS;
                    SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
                }
                break;
        }
    }
    UNLOCK_MUTEX();
}

void Win_Serial::setParity(ParityType parity)
{
    LOCK_MUTEX();
    if (m_PortSettings.Parity!=parity)
    {
        m_PortSettings.Parity=parity;
    }
    if (isOpen()) {
        Win_CommConfig.dcb.Parity=(unsigned char)parity;
        switch (parity)
        {

            /*space parity*/
            case PAR_SPACE:
                if (m_PortSettings.DataBits==DATA_8)
                {
                }
                Win_CommConfig.dcb.fParity=TRUE;
                break;

            /*mark parity - WINDOWS ONLY*/
            case PAR_MARK:
                Win_CommConfig.dcb.fParity=TRUE;
                break;

            /*no parity*/
            case PAR_NONE:
                Win_CommConfig.dcb.fParity=FALSE;
                break;

            /*even parity*/
            case PAR_EVEN:
                Win_CommConfig.dcb.fParity=TRUE;
                break;

            /*odd parity*/
            case PAR_ODD:
                Win_CommConfig.dcb.fParity=TRUE;
                break;
        }
        SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));
    }
    UNLOCK_MUTEX();
}

void Win_Serial::setTimeout(long millisec)
{
    LOCK_MUTEX();
    m_PortSettings.Timeout_Millisec = millisec;

    if (millisec == -1)
    {
        Win_CommTimeouts.ReadIntervalTimeout = MAXDWORD;
        Win_CommTimeouts.ReadTotalTimeoutConstant = 0;
    } else {
        Win_CommTimeouts.ReadIntervalTimeout = millisec;
        Win_CommTimeouts.ReadTotalTimeoutConstant = millisec;
    }
    Win_CommTimeouts.ReadTotalTimeoutMultiplier = 0;
    Win_CommTimeouts.WriteTotalTimeoutMultiplier = millisec;
    Win_CommTimeouts.WriteTotalTimeoutConstant = 0;
    if (queryMode() != QSerialBase::EventDriven)
        SetCommTimeouts(Win_Handle, &Win_CommTimeouts);

    UNLOCK_MUTEX();
}

bool Win_Serial::open(OpenMode mode)
{
    unsigned long confSize = sizeof(COMMCONFIG);
    Win_CommConfig.dwSize = confSize;
    DWORD dwFlagsAndAttributes = 0;
    if (queryMode() == QSerialBase::EventDriven)
        dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED;

    LOCK_MUTEX();
    if (mode == QIODevice::NotOpen)
        return isOpen();
    if (!isOpen()) {
        /*open the port*/
        Win_Handle=CreateFileA(m_strSerialName.toAscii(), GENERIC_READ|GENERIC_WRITE,
                              0, NULL, OPEN_EXISTING, dwFlagsAndAttributes, NULL);
        if (Win_Handle!=INVALID_HANDLE_VALUE)
        {
            QIODevice::open(mode);
            /*configure port settings*/
            GetCommConfig(Win_Handle, &Win_CommConfig, &confSize);
            GetCommState(Win_Handle, &(Win_CommConfig.dcb));

            /*set up parameters*/
            Win_CommConfig.dcb.fBinary=1;
            Win_CommConfig.dcb.fInX=0;
            Win_CommConfig.dcb.fOutX=0;
            Win_CommConfig.dcb.fAbortOnError=0;
            Win_CommConfig.dcb.fNull=0;
            setBaudRate(m_PortSettings.BaudRate);
            setDataBits(m_PortSettings.DataBits);
            setStopBits(m_PortSettings.StopBits);
            setParity(m_PortSettings.Parity);
            setFlowControl(m_PortSettings.FlowControl);
            setTimeout(m_PortSettings.Timeout_Millisec);
            SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG));

            //init event driven approach
            if (queryMode() == QSerialBase::EventDriven)
            {
                Win_CommTimeouts.ReadIntervalTimeout = MAXDWORD;
                Win_CommTimeouts.ReadTotalTimeoutMultiplier = 0;
                Win_CommTimeouts.ReadTotalTimeoutConstant = 0;
                Win_CommTimeouts.WriteTotalTimeoutMultiplier = 0;
                Win_CommTimeouts.WriteTotalTimeoutConstant = 0;
                SetCommTimeouts(Win_Handle, &Win_CommTimeouts);
                if (!SetCommMask( Win_Handle,EV_RXCHAR))
                {
                    UNLOCK_MUTEX();
                    return false;
                }
                overlapThread->start();
            }
        }
    } else {
        UNLOCK_MUTEX();
        return false;
    }
    UNLOCK_MUTEX();
    return isOpen();
}

void Win_Serial::close()
{
    LOCK_MUTEX();
    if (isOpen())
    {
        flush();
        if (overlapThread->isRunning())
        {
            overlapThread->stop();
            if (QThread::currentThread() != overlapThread)
                overlapThread->wait();
        }
        if (CloseHandle(Win_Handle))
            Win_Handle = INVALID_HANDLE_VALUE;
        _bytesToWrite = 0;
        QIODevice::close();
    }

    UNLOCK_MUTEX();
}

void Win_Serial::flush() {
    LOCK_MUTEX();
    if (isOpen())
    {
        FlushFileBuffers(Win_Handle);
    }
    UNLOCK_MUTEX();
}

void Win_Serial::monitorCommEvent()
{
    DWORD eventMask = 0;

    ResetEvent(overlap.hEvent);
    WaitCommEvent(Win_Handle, & eventMask, & overlap);

    if (WaitForSingleObject(overlap.hEvent, INFINITE) == WAIT_OBJECT_0)
    {
        //overlap event occured
        DWORD undefined;
        if (!GetOverlappedResult(Win_Handle, & overlap, & undefined, false))
        {
            return;
        }
        if (eventMask & EV_RXCHAR)
        {
            if (sender() != this)
            {
                emit readyRead();//mark
            }
        }
    }
}

void Win_Serial::terminateCommWait()
{
    SetCommMask(Win_Handle, 0);
}

ulong Win_Serial::lineStatus(void)
{
    unsigned long Status=0, Temp=0;
    LOCK_MUTEX();
    if (isOpen())
    {
        GetCommModemStatus(Win_Handle, &Temp);
        if (Temp&MS_CTS_ON)
        {
            Status|=LS_CTS;
        }
        if (Temp&MS_DSR_ON)
        {
            Status|=LS_DSR;
        }
        if (Temp&MS_RING_ON)
        {
            Status|=LS_RI;
        }
        if (Temp&MS_RLSD_ON)
        {
            Status|=LS_DCD;
        }
    }
    UNLOCK_MUTEX();
    return Status;
}

qint64 Win_Serial::bytesToWrite() const
{
    return _bytesToWrite;
}

qint64 Win_Serial::readData(char *data, qint64 maxSize)
{
    DWORD retVal;

    LOCK_MUTEX();

    retVal = 0;
    if (queryMode() == QSerialBase::EventDriven)
    {
        OVERLAPPED overlapRead;
        overlapRead.Internal = 0;
        overlapRead.InternalHigh = 0;
        overlapRead.Offset = 0;
        overlapRead.OffsetHigh = 0;
        overlapRead.hEvent = CreateEvent(NULL, true, false, NULL);
        if (!ReadFile(Win_Handle, (void*)data, (DWORD)maxSize, & retVal, & overlapRead))
        {
            if (GetLastError() == ERROR_IO_PENDING)
                GetOverlappedResult(Win_Handle, & overlapRead, & retVal, true);
            else
            {
                m_ulLastErr = E_READ_FAILED;
                retVal = (DWORD)-1;
            }
        }
        CloseHandle(overlapRead.hEvent);
    }
    else if (!ReadFile(Win_Handle, (void*)data, (DWORD)maxSize, & retVal, NULL))
    {
        m_ulLastErr = E_READ_FAILED;
        retVal = (DWORD)-1;
    }

    UNLOCK_MUTEX();

    return (qint64)retVal;
}

qint64 Win_Serial::writeData(const char *data, qint64 maxSize)
{
    flush();
    DWORD retVal;

    LOCK_MUTEX();

    retVal = 0;
    if (queryMode() == QSerialBase::EventDriven)
    {
        bytesToWriteLock->lockForWrite();
        _bytesToWrite += maxSize;
        bytesToWriteLock->unlock();
        overlapWrite.Internal = 0;
        overlapWrite.InternalHigh = 0;
        overlapWrite.Offset = 0;
        overlapWrite.OffsetHigh = 0;
        overlapWrite.hEvent = CreateEvent(NULL, true, false, NULL);
        if (!WriteFile(Win_Handle, (void*)data, (DWORD)maxSize, & retVal, & overlapWrite))
        {
            m_ulLastErr = E_WRITE_FAILED;
            retVal = (DWORD)-1;
        } else
        {
            retVal = maxSize;
        }
    }
    else if (!WriteFile(Win_Handle, (void*)data, (DWORD)maxSize, & retVal, NULL))
    {
        m_ulLastErr = E_WRITE_FAILED;
        retVal = (DWORD)-1;
    }

    UNLOCK_MUTEX();

    return (qint64)retVal;
}

qint64 Win_Serial::size() const
{
    int availBytes;
    COMSTAT Win_ComStat;
    DWORD Win_ErrorMask=0;
    ClearCommError(Win_Handle, &Win_ErrorMask, &Win_ComStat);
    availBytes = Win_ComStat.cbInQue;
    return (qint64)availBytes;
}

void Win_Serial::setDtr(bool set)
{
    LOCK_MUTEX();
    if (isOpen())
    {
        if (set)
        {
            EscapeCommFunction(Win_Handle, SETDTR);
        }
        else
        {
            EscapeCommFunction(Win_Handle, CLRDTR);
        }
    }
    UNLOCK_MUTEX();
}

void Win_Serial::setRts(bool set)
{
    LOCK_MUTEX();
    if (isOpen())
    {
        if (set)
        {
            EscapeCommFunction(Win_Handle, SETRTS);
        }
        else
        {
            EscapeCommFunction(Win_Handle, CLRRTS);
        }
    }
    UNLOCK_MUTEX();
}

qint64 Win_Serial::bytesAvailable()
{
    LOCK_MUTEX();
    if (isOpen())
    {
        DWORD Errors;
        COMSTAT Status;
        bool success=ClearCommError(Win_Handle, &Errors, &Status);
        translateError(Errors);
        if (success)
        {
            m_ulLastErr=E_NO_ERROR;
            UNLOCK_MUTEX();
            return Status.cbInQue + QIODevice::bytesAvailable();
        }
        UNLOCK_MUTEX();
        return (unsigned int)-1;
    }
    UNLOCK_MUTEX();
    return 0;
}

void Win_Serial::translateError(ulong error)
{
    if (error&CE_BREAK)
    {
        m_ulLastErr=E_BREAK_CONDITION;
    }
    else if (error&CE_FRAME)
    {
        m_ulLastErr=E_FRAMING_ERROR;
    }
    else if (error&CE_IOE)
    {
        m_ulLastErr=E_IO_ERROR;
    }
    else if (error&CE_MODE)
    {
        m_ulLastErr=E_INVALID_FD;
    }
    else if (error&CE_OVERRUN)
    {
        m_ulLastErr=E_BUFFER_OVERRUN;
    }
    else if (error&CE_RXPARITY)
    {
        m_ulLastErr=E_RECEIVE_PARITY_ERROR;
    }
    else if (error&CE_RXOVER)
    {
        m_ulLastErr=E_RECEIVE_OVERFLOW;
    }
    else if (error&CE_TXFULL)
    {
        m_ulLastErr=E_TRANSMIT_OVERFLOW;
    }
}









Win_SerialThread::Win_SerialThread(Win_Serial * qesp):
    QThread()
{
    this->qesp = qesp;
    terminate = false;
}

void Win_SerialThread::stop()
{
    terminate = true;
    qesp->terminateCommWait();
}

void Win_SerialThread::run()
{
    while (!terminate)
        qesp->monitorCommEvent();
    terminate = false;
}
